/* ForEachListDataListener.java

	Purpose:
		
	Description:
		
	History:
		10:45:00 AM Jan 27, 2015, Created by jameschu

Copyright (C) 2015 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.zuti.zul;

import java.util.List;
import java.util.ListIterator;

import org.zkoss.zuti.ForEachRenderer;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.ShadowElement;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.sys.ShadowElementsCtrl;
import org.zkoss.zk.ui.util.Template;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.event.ListDataEvent;
import org.zkoss.zul.event.ListDataListener;

/**
 * listen the model data onChange event (support list model in ForEach)
 *
 * @author jameschu
 * @since 8.0.0
 */
public class ForEachListDataListener implements ListDataListener, java.io.Serializable {
    private static final long serialVersionUID = 20150116151900L;
    private final ForEach _forEachComp;
    private final Component _host;
    private final ForEachConverter _conv;
    private final Template _tm;

    public ForEachListDataListener(ForEach forEachComp, Component host) {
        this._forEachComp = forEachComp;
        this._host = host;
        this._conv = _forEachComp.getDataConverter();
        Template tm = _forEachComp.getTemplate("");
        this._tm = tm;
    }

    public void onChange(ListDataEvent event) {
        onListModelDataChange(new ForEachListModelDataEvent(event));
    }

    @SuppressWarnings("unchecked")
    private void onListModelDataChange(ListDataEvent event) {
        // Bug ZK-2837
        if (_host == null || _host.getDesktop() == null) {
            return;
        }

        final ListModel<?> model = event.getModel();
        int type = event.getType();
        int index0 = event.getIndex0();
        int index1 = event.getIndex1();

        List<Component[]> feCompList = (List<Component[]>) _host
                .getAttribute(TemplateBasedShadowElement.FOREACH_RENDERED_COMPONENTS + _forEachComp.getUuid());
        int oldsz = feCompList == null ? 0 : feCompList.size();
        int newsz = model.getSize();
        Object shadowInfo = ShadowElementsCtrl.getCurrentInfo();
        try {
            ShadowElementsCtrl.setCurrentInfo(_forEachComp);
            if (type == ListDataEvent.INTERVAL_ADDED) {
                int addedCount = index1 - index0 + 1;
                if ((newsz - oldsz) <= 0)
                    throw new UiException("Adding causes a smaller list?");
                else if ((oldsz + addedCount) != newsz) { //check live data changed
                    index0 = oldsz;
                    index1 = newsz - 1;
                }
                renderModelData(_host, model, index0, index1);
            } else if (type == ListDataEvent.CONTENTS_CHANGED) {
                // to optimize the performance for ZK-2800, we won't re-sync all model for list data type, but map type
                if (index0 < 0) {
                    syncModel(_host, model);
                } else {
                    for (int i = index0; i <= index1; i++) {
                        for (Component oldComps : feCompList.get(i)) {
                            if (oldComps instanceof ShadowElement)
                                ((ShadowElement) oldComps).getDistributedChildren().clear();
                            oldComps.detach();
                        }
                        feCompList.remove(i);
                    }
                    renderModelData(_host, model, index0, index1);
                }
            } else if (type == ListDataEvent.INTERVAL_REMOVED) {
                if (oldsz - newsz <= 0)
                    throw new UiException("Removal causes a larger list?");
                for (int i = index0; i <= index1; i++) {
                    for (Component oldComps : feCompList.get(index0)) {
                        if (oldComps instanceof ShadowElement)
                            ((ShadowElement) oldComps).getDistributedChildren().clear();
                        oldComps.detach();
                    }
                    feCompList.remove(index0);
                }
            }
        } finally {
            ShadowElementsCtrl.setCurrentInfo(shadowInfo);
        }
    }

    @SuppressWarnings("unchecked")
    private void syncModel(Component host, ListModel<?> model) {
        //clear all
        String forEachRenderedCompAttr = TemplateBasedShadowElement.FOREACH_RENDERED_COMPONENTS
                + _forEachComp.getUuid();
        List<Component[]> feCompList = (List<Component[]>) host.getAttribute(forEachRenderedCompAttr);
        if (feCompList != null) {
            host.removeAttribute(forEachRenderedCompAttr);
            for (ListIterator<Component> l = _forEachComp.getDistributedChildren().listIterator(); l.hasNext(); ) {
                l.next();
                l.remove();
            }
        }
        renderModelData(host, model, 0, model.getSize() - 1);
    }

    @SuppressWarnings("unchecked")
    private void renderModelData(Component host, ListModel<?> model, int from, int to) {
        try {
            ForEachRenderer renderer = (ForEachRenderer) Class.forName("cn.easyplatform.web.task.api.ForEachRenderer").newInstance();
            if (_conv != null) {
                List<Object> data = (List<Object>) _conv.coerceToUi(model);
                if (data != null) {
                    int size = data.size();
                    if (to >= size)
                        to = size - 1;
                    renderer.render(_forEachComp, _host, _tm, from, to, _forEachComp.getStep(), data, true);
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new UiException("Wrong ForEachRenderer");
        }
    }
}
